﻿using System;
using System.IO;
using System.Linq;

namespace Shen.NET.REPL
{
    class ReplMain
    {
        static void Main()
        {
            Console.ForegroundColor = ConsoleColor.Gray;
            WriteLine(@"| |/ /\ \       |  __ \|  ____|  __ \| |     
| ' /  \ \      | |__) | |__  | |__) | |     
|  <    > \     |  _  /|  __| |  ___/| |     
| . \  / ^ \    | | \ \| |____| |    | |____ 
|_|\_\/_/ \_\   |_|  \_\______|_|    |______|", ConsoleColor.Magenta);
            ShowHelp();
            var context = Builtins.Install(Context.NewGlobal());

            for (;;)
            {
                Console.Write("KL> ");
                var line = (Console.ReadLine() ?? "").Trim();

                if (line == "quit" || line == "exit")
                    return;

                if (line == "help")
                {
                    ShowHelp();
                }
                else if (line.StartsWith("load"))
                {
                    var file = line.Substring(4).Trim();

                    if (file == "all")
                    {
                        foreach (var klFile in KlFiles)
                            Load(klFile, context);
                    }
                    else
                    {
                        Load(file, context);
                    }
                }
                else if (line == "reset")
                {
                    context = Builtins.Install(Context.NewGlobal());
                }
                else if (line.StartsWith("source"))
                {
                    var name = line.Substring(6).Trim();
                    var val = context.Resolve(name);

                    if (val is Symbol)
                        val = val.As<Symbol>().Value;

                    if (val is Defun)
                        val = val.As<Defun>().DefunExpr;

                    if (val is Expr)
                    {
                        var expr = val.As<Expr>();
                        WriteLine(expr.Token.Location, ConsoleColor.Cyan);
                        WriteLine(expr.Token, ConsoleColor.White);
                    }
                    else
                    {
                        WriteLine("Not an expression", ConsoleColor.Yellow);
                    }
                }
                else if (line == "shen")
                {
                    foreach (var klFile in KlFiles)
                        Load(klFile, context);

                    Run("(shen.shen)", context);
                }
                else
                {
                    Run(line, context);
                }
            }
        }

        static void Run(String line, Context context)
        {
            try
            {
                var result = Syntax.Read(line).Parse(true).Eval(context);
                WriteLine(result, result is Error ? ConsoleColor.Yellow : ConsoleColor.Green);
            }
            catch (Exception exc)
            {
                WriteLine(exc.Message, ConsoleColor.Red);
            }
        }

        static readonly String KlFolder = Path.Combine(Environment.CurrentDirectory, @".\..\..\..\klambda\");
        static readonly String[] KlFiles = new[] {
            "toplevel", "core", "sys", "sequent", "yacc", "reader", "prolog",
            "track", "load", "writer", "macros", "declarations", "t-star", "types"
        };

        static void Load(String file, Context context)
        {
            if (! file.EndsWith(".kl")) file = file + ".kl";
            var klFile = Path.Combine(KlFolder, file);

            foreach (var expr in Syntax.ReadFile(Path.GetFullPath(klFile)).Select(x => x.Parse(true)))
            {
                if (expr is DefunExpr || expr is AppExpr)
                {
                    WriteLine(TokenToString(expr.Token), ConsoleColor.White);
                    expr.Eval(context);
                }
            }
        }

        static string TokenToString(Token t)
        {
            return t is Atom
                ? t.ToString()
                : (t.As<Combo>().Tokens.Count >= 2
                    ? t.As<Combo>().Tokens[0] + " " + t.As<Combo>().Tokens[1]
                    : t.ToString());
        }

        static void ShowHelp()
        {
            WriteLine("exit, quit      = exit repl", ConsoleColor.Cyan);
            WriteLine("load <name>     = loads kl file", ConsoleColor.Magenta);
            WriteLine("load all        = loads all kl files in pre-determined order", ConsoleColor.Cyan);
            WriteLine("source <symbol> = shows source code and location of function", ConsoleColor.Magenta);
            WriteLine("shen            = loads all kl files and starts the Shen REPL", ConsoleColor.Cyan);
            WriteLine("reset           = clears the global scope and re-installs built-ins", ConsoleColor.Magenta);
            WriteLine("help            = show this message", ConsoleColor.Cyan);
        }

        static void WriteLine(Object obj, ConsoleColor color)
        {
            var prevColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.WriteLine(obj == null ? "null" : obj.ToString());
            Console.ForegroundColor = prevColor;
        }
    }
}
